package cn.lg.soar.gateway.filter;

import cn.lg.soar.common.exceptions.AuthenticationException;
import cn.lg.soar.common.exceptions.PermissionException;
import cn.lg.soar.common.model.TokenInfo;
import cn.lg.soar.common.util.current.CurrentUser;
import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.data.RandomUtil;
import cn.lg.soar.common.util.data.StringUtil;
import cn.lg.soar.common.util.token.SoarJWT;
import cn.lg.soar.core.manager.ITokenStateManager;
import cn.lg.soar.core.manager.PermitLevelManager;
import cn.lg.soar.core.manager.StateTokenManager;
import cn.lg.soar.core.manager.TokenManager;
import cn.lg.soar.core.util.InnerSerializeUtils;
import cn.lg.soar.gateway.util.RequestUtils;
import cn.lg.soar.system.api.enums.PermitLevelEnum;
import cn.lg.soar.system.api.manager.AuthenticationManager;
import cn.lg.soar.system.api.model.AuthProps;
import cn.lg.soar.system.api.service.IUserApi;
import com.alibaba.fastjson.JSONObject;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;

/**
 * 网关鉴权
 * @author ruoyi
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private final PermitLevelManager permitLevelManager;
    private final TokenManager tokenManager;
    private final AuthenticationManager authenticationManager;
    private final IUserApi userApi;

    private final static int LOGIN = 0;
    private final static int ANON = 1;
    private final static int AUTH = 2;
    private final static int PERMIT = 0x200;
    private final static int AUTH_BY_TYPE_BEGIN = 0x100;


    /**
     * 安全访问处理（微服务内部调用安全控制）
     */
    private Consumer<ServerHttpRequest.Builder> securityHandler;

    @Value("${lg.cloud.access-ctrl.secret:}")
    private String secret;
    @Value("${lg.cloud.access-ctrl.expire:300s}")
    private Duration duration;

    @PostConstruct
    public void setSecret() {
        if (DataUtil.isValuable(secret)) {
            long time = duration.toMillis();
            byte[] bytes = secret.getBytes();
            securityHandler = (mutate) -> {
                String random = RandomUtil.getString(32);
                String expire = String.valueOf(System.currentTimeMillis() + time);
                // 数据签名
                MessageDigest digest;
                try {
                    digest = MessageDigest.getInstance("MD5");
                } catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException(e);
                }
                digest.update(bytes);
                digest.update(random.getBytes());
                digest.update(expire.getBytes());
                String sign = DataUtil.byteToHex(digest.digest());

                mutate.header("security-random", random);
                mutate.header("security-expire", expire);
                mutate.header("security-sign", sign);
            };
        } else {
            securityHandler = (a) -> {};
        }
    }

    public AuthFilter(
            @Lazy AuthProps authProps,
            @Lazy IUserApi userApi,
            @Lazy AuthenticationManager authenticationManager,
            @Lazy ITokenStateManager tokenStateManager
    ) {
        Map<PermitLevelEnum, Set<String>> permitMapper = authProps.getPermitMapper();
        PermitLevelEnum permitMapperDefault = authProps.getPermitMapperDefault();
        Set<Map.Entry<PermitLevelEnum, Set<String>>> entries = permitMapper.entrySet();
        // 权限级别管理器
        PermitLevelManager permitLevelManager = new PermitLevelManager();
        permitLevelManager.putLevel(LOGIN, authProps.getLoginPaths());
        for (Map.Entry<PermitLevelEnum, Set<String>> entry : entries) {
            if (Objects.equals(permitMapperDefault, entry.getKey())) {
                continue;
            }
            switch (entry.getKey()) {
                case permit:
                    permitLevelManager.putLevel(PERMIT, entry.getValue());
                    break;
                case auth:
                    permitLevelManager.putLevel(AUTH, entry.getValue());
                    break;
                case anon:
                    permitLevelManager.putLevel(ANON, entry.getValue());
                    break;
            }
        }
        this.permitLevelManager = permitLevelManager;

        this.authenticationManager = authenticationManager;
        this.userApi = userApi;
        // 处理用户类型权限级别
        Map<Integer, Set<String>> userTypeAuthPermit = authProps.getUserTypeAuthPermit();
        userTypeAuthPermit.forEach((k, v) -> {
            permitLevelManager.putLevel(AUTH_BY_TYPE_BEGIN | k, v);
        });

        // 基于redis的有状态token管理器
        this.tokenManager = new StateTokenManager(
                new SoarJWT(authProps.getAccessTokenSecret(), authProps.getAccessTokenExpire()),
                tokenStateManager
        );

        // 无状态token管理器
//        this.tokenManager = new TokenManager(new SoarJWT(accessToken.getSecret(), accessToken.getExpire()));
    }

    /**
     * 登录拦截：
     * 1.授权失败直接响应失败
     * 2.授权成功：根据授权中心发放的授权码生成访问token再响应
     *
     * 权限拦截：
     * A.token无效 或 无token：是匿名请求路径直接放行
     * B.token有效：
     *   1.设置请求头
     *   2.判断路径权限
     *       登录即可访问的路径放行
     *       需要指定权限的路径：验证权限
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String permit = request.getMethod().name().toUpperCase() + request.getURI().getPath();
        // 1.设置用户真实ip
        String remoteIp = RequestUtils.getRemoteIp(request);
        ServerHttpRequest.Builder mutate = request.mutate();
        RequestUtils.setClientIp(mutate, remoteIp);
        // 内部调用安全控制
        securityHandler.accept(mutate);

        int permitLevel = permitLevelManager.match(permit);
        // 是否是登录请求
        if (permitLevel == LOGIN) {
            return loginFilter(exchange, chain, remoteIp);
        }

        // 是否有token
        CurrentUser currentUser;
        String token = RequestUtils.getHeaderOrQueryParams(request, "token");
        if (token == null) {
            currentUser = null;
        } else {
            currentUser = tokenManager.getUserInfo(token, remoteIp);
        }

        // token是否有效
        if (currentUser == null) {
            if (permitLevel == ANON) {
                return chain.filter(exchange);
            }
            // token无效，需先登录
            throw new AuthenticationException();
        }

        // 保存用户信息
        String userInfo = InnerSerializeUtils.toJson(currentUser);
        RequestUtils.addHeader(mutate, CurrentUser.USER_KEY, userInfo);

        // 是否不需要指定权限
        if (permitLevel == ANON || permitLevel == AUTH) {
            return chain.filter(exchange);
        }

        // 是否是指定用户类型可访问的路径
        Integer userType = currentUser.getUserType();
        if (userType.equals(permitLevel - AUTH_BY_TYPE_BEGIN)) {
            return chain.filter(exchange);
        }

        // 是否拥有指定权限
        if (authenticationManager.hasPermit(currentUser.getId(), permit)) {
            return chain.filter(exchange);
        }

        // 是否是超级管理员
        if (userApi.isSuperAdministrator(currentUser.getId())) {
            return chain.filter(exchange);
        }

        // 无权限
        throw new PermissionException();
    }

    /**
     * 登录接口处理
     * @param exchange
     * @param chain
     * @param remoteIp
     * @return
     */
    private Mono<Void> loginFilter(ServerWebExchange exchange, GatewayFilterChain chain, String remoteIp) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();

        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;

                    // 合并所有 DataBuffer
                    return DataBufferUtils.join(fluxBody)
                            .flatMap(dataBuffer -> {
                                try {
                                    // 读取响应内容
                                    byte[] content = new byte[dataBuffer.readableByteCount()];
                                    dataBuffer.read(content);
                                    DataBufferUtils.release(dataBuffer); // 释放内存

                                    // 解析 JSON
                                    String responseBody = new String(content, StandardCharsets.UTF_8);
                                    JSONObject jsonObject = InnerSerializeUtils.parseObject(responseBody);

                                    // 处理成功响应
                                    if (Boolean.TRUE.equals(jsonObject.get("success"))) {
                                        String refreshToken = jsonObject.getString("data");
                                        if (DataUtil.isValuable(refreshToken)) {
                                            // 生成新的 Token
                                            String accessToken = tokenManager.create(refreshToken, remoteIp);
                                            TokenInfo tokenInfo = new TokenInfo();
                                            tokenInfo.setRefreshToken(refreshToken);
                                            tokenInfo.setAccessToken(accessToken);

                                            // 更新响应内容
                                            jsonObject.put("data", tokenInfo);
                                            byte[] updatedContent = jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8);
                                            return super.writeWith(Flux.just(bufferFactory.wrap(updatedContent)));
                                        }
                                    }

                                    // 返回原始响应内容
                                    return super.writeWith(Flux.just(bufferFactory.wrap(content)));
                                } catch (Exception e) {
                                    // 异常处理
                                    return Mono.error(e);
                                }
                            });
                }
                return super.writeWith(body);
            }
        };

        // 使用装饰后的响应继续过滤链
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
